var express = require('express');
var Excel = require("exceljs");
var router = express.Router();
var mongoose = require('mongoose');
var superagent = require('superagent');
const SchemaDef = require('../models/SchemaDef');
const Category = require('../models/Category');
const Relation = require('../models/Relation');
const Instance = require('../models/Instance');
const RelationInstance = require('../models/RelationInstance');
const RelationType = require('../models/RelationType');
const Tag = require('../models/Tag');
const SchemaIcon = require('../models/SchemaIcon');
var tools = require('../utils/tools')
var cache = require('../utils/cache')
var formidable = require('formidable');
var fs = require('fs');
var os = require('os');
var path = require("path");
var url = require("url")
const mineType = require("mime-types");
var settings = require('../settings')
var log = require('../utils/logs').getLogger();

var CACHE_INSTANCE = 'schemas'

/**
 * @swagger
 *
 * /cmdb/schemas/list:
 *   get:
 *     tags:
 *       - 模型管理
 *     summary: 所有模型
 *     description: 所有模型
 *     responses:
 *       200:
 *         description: 所有模型
 */
router.get('/list', async function (req, res, next) {
  let result = await SchemaDef.find({},{_id: 1, code: 1, name: 1})
  return res.json({ status: 'success', data: result})
});

/**
 * @swagger
 *
 * /cmdb/schemas/page:
 *   get:
 *     tags:
 *       - 模型管理
 *     summary: 分页查询模型
 *     description: 支持标准方式查询。$_foo=bar
 *     responses:
 *       200:
 *         description: 返回标准分页结构
 */
router.get('/page', async function (req, res, next) {
  let result = await SchemaDef.page(tools.queryResolver(req.query), req.query.page, req.query.size)
  for (let i = 0; i < result.docs.length; i++) {
    let doc = { ...result.docs[i]._doc }
    let schemaIcon = await SchemaIcon.findOne({ schemadef: doc._id })
    if (schemaIcon) {
      doc.icon = schemaIcon.url
    } else {
      doc.icon = ''
    }

    result.docs[i] = doc
  }
  return res.json({ status: 'success', data: result.docs, pageable: result.pageable })
});

/**
 * @swagger
 *
 * /cmdb/schemas/tree:
 *   get:
 *     tags:
 *       - 模型管理
 *     summary: 模型树
 *     description: 模型树，数据量较大，API层缓存1分钟
 *     responses:
 *       200:
 *         description: 节点关键字，code、name、children。节点包含属性定义
 */
router.get('/tree', async function (req, res, next) {

  var cached = await cache.exists(CACHE_INSTANCE, 'tree')

  if (cached) {
    var docs = await cache.get(CACHE_INSTANCE, 'tree')
    return res.json({ status: 'success', data: docs })
  }

  var categoryAggre = Category.aggregate([
    {
      $match: {
        $or: [{ parent: { $exists: false } }, { parent: null }, { parent: '' }]
      }
    },
    {
      $lookup:
      {
        from: "categories",
        localField: "code",
        foreignField: "parent",
        as: "children"
      }
    }
  ])

  var schemaDefAggre = SchemaDef.aggregate([
    {
      $group: {
        _id: "$category",
        children: {
          $push: {
            _id: "$_id",
            code: "$code",
            name: "$name",
            attrs: "$attrs",
            relations: "$relations"
          }
        }
      }
    }
  ])

  categoryAggre.exec((err, docs) => {
    if (err) {
      return res.json({ status: 'failure', data: err })
    }

    schemaDefAggre.exec((err1, docs1) => {
      if (err1) {
        return res.json({ status: 'failure', data: err1 })
      }
      docs.forEach(d => {
        d.children.forEach(c => {
          let s = docs1.find(s => s._id === c.code)
          if (s && s.children.length > 0) {
            c.children = s.children
          }
        })
      })

      cache.set(CACHE_INSTANCE, 'tree', docs, 60 * 1000) // 缓存60秒

      return res.json({ status: 'success', data: docs })
    })
  })
})

/**
 * @swagger
 *
 * /cmdb/schemas/tree/pure:
 *   get:
 *     tags:
 *       - 模型管理
 *     summary: 模型树（纯净版）
 *     description: 模型树（纯净版），API层缓存1分钟
 *     responses:
 *       200:
 *         description: 节点关键字，code、name、children。节点不包含属性定义
 */
router.get('/tree/pure', async function (req, res, next) {

  var cached = await cache.exists(CACHE_INSTANCE, 'puretree')

  if (cached) {
    var docs = await cache.get(CACHE_INSTANCE, 'puretree')
    return res.json({ status: 'success', data: docs })
  }

  const categoryAggre = Category.aggregate([
    {
      $match: {
        $or: [{ parent: { $exists: false } }, { parent: null }, { parent: '' }]
      }
    },
    {
      $lookup:
      {
        from: "categories",
        localField: "code",
        foreignField: "parent",
        as: "children"
      }
    }
  ])

  const schemaDefAggre = SchemaDef.aggregate([
    {
      $group: {
        _id: "$category",
        children: {
          $push: {
            _id: "$_id",
            code: "$code",
            name: "$name",
            attrs: "$attrs",
            relations: "$relations"
          }
        }
      }
    }
  ])

  categoryAggre.exec((err, docs) => {
    if (err) {
      return res.json({ status: 'failure', data: err })
    }

    schemaDefAggre.exec((err1, docs1) => {
      if (err1) {
        return res.json({ status: 'failure', data: err1 })
      }

      var data = docs.map(doc => {
        var d = {
          code: doc.code,
          name: doc.name,
          children: doc.children.map(c => {
            var d1 = {
              code: c.code,
              name: c.name,
              children: []
            }

            let s = docs1.find(s => s._id === c.code)
            if (s && s.children.length > 0) {
              d1.children = s.children.map(it => {
                return {
                  code: it.code,
                  name: it.name
                }
              })
            }

            return d1
          })
        }

        return d
      })

      cache.set(CACHE_INSTANCE, 'puretree', data, 60 * 1000) // 缓存60秒
      return res.json({ status: 'success', data: data })
    })
  })
})

/**
 * @swagger
 *
 * /cmdb/schemas/groups:
 *   get:
 *     tags:
 *       - 模型管理
 *     summary: 按类型（二级分类）分组返回所有模型
 *     description:  按类型（二级分类）分组返回所有模型
 *     responses:
 *       200:
 *         description: 按类型（二级分类）分组返回所有模型
 */
router.get('/groups', function (req, res, next) {

  Category.aggregate([
    {
      $match: {
        parent: { $exists: true }
      }
    },
    {
      $lookup:
      {
        from: "schemadefs",
        localField: "code",
        foreignField: "category",
        as: "children"
      }
    }
  ], (err, docs) => {
    if (err) return tools.handleError(res, err);
    const data = docs.filter(it => it.children.length > 0).map(doc => {
      const parent = { label: doc.name, value: doc.code }
      parent.children =
        doc.children.map(child => {
          return { label: child.name, value: child._id }
        })
      return parent
    })
    return res.json({ status: 'success', data: data })
  })

});

/**
 * @swagger
 *
 * /cmdb/schemas/outline:
 *   get:
 *     tags:
 *       - 模型管理
 *     summary: 模型摘要
 *     description:  模型摘要，按类型（二级分类）分组返回所有模型，包含模型图标和模型实例数量信息
 *     responses:
 *       200:
 *         description: 模型摘要
 */
router.get('/outline', async function (req, res, next) {
  var docs = await Category.aggregate([
    {
      $match: {
        parent: { $exists: true }
      }
    },
    {
      $lookup:
      {
        from: "schemadefs",
        localField: "code",
        foreignField: "category",
        as: "children"
      }
    }
  ]);

  const data = docs.filter(it => it.children.length > 0).map(doc => {
    const parent = { label: doc.name, value: doc.code, children: doc.children }
    parent.children =
      doc.children.map(child => {
        return { label: child.name, value: child._id }
      })
    return parent
  })

  for (var i = 0; i < data.length; i++) {
    for (var j = 0; j < data[i].children.length; j++) {
      var instanceCount = await Instance.count({ schemadef: data[i].children[j].value })
      var icon = await SchemaIcon.findOne({ schemadef: data[i].children[j].value })
      data[i].children[j].count = instanceCount
      data[i].children[j].icon = icon ? icon.url : ''
    }
  }

  return res.json({ status: 'success', data: data })
})

/**
 * @swagger
 *
 * /cmdb/schemas/count:
 *   get:
 *     tags:
 *       - 模型管理
 *     summary: 模型数量
 *     description:  模型数量
 *     responses:
 *       200:
 *         description: 模型数量
 */
router.get('/count', async function (req, res, next) {
  var count = await SchemaDef.count()
  return res.json({ status: 'success', data: count })
});

/**
 * @swagger
 *
 * /cmdb/schemas/category/{catecode}:
 *   get:
 *     tags:
 *       - 模型管理
 *     summary: 按分类查询模型
 *     description:  按分类查询模型
 *     parameters:
 *       - name: catecode
 *         description: 分类编码
 *         in: path
 *         required: true
 *         type: string
 *     responses:
 *       200:
 *         description: 按分类查询模型
 */
router.get('/category/:catecode', async function (req, res, next) {
  var reqCateCodes =  req.params.catecode.split(',')
  var reqCategories = await Category.find({code: {$in: reqCateCodes } })
  if(!reqCategories) {
    return res.json({ status: 'success', data: 0 })
  }
  var categories = []
  for(var i=0; i<reqCategories.length; i++) {
    if(!reqCategories[i].parent) {
      var subCategories = await Category.find({parent: reqCategories[i].code})
      if(subCategories) {
        subCategories.forEach(it => categories.push(it))
      }
    } else {
      categories.push(reqCategories[i])
    }
  }
  
  var categoryCodes = categories.map(it => it.code)

  var schemadefs = await SchemaDef.find({category: { $in: categoryCodes } })
  schemadefs.forEach(it => {
    var hisCate = categories.find(c => c.code === it.category)
    if(hisCate) {
      hisCate.attrs.forEach(cAttr => {
        it.attrs.push(cAttr)
      })
    }
  })
  return res.json({ status: 'success', data: schemadefs })
});

/**
 * @swagger
 *
 * /cmdb/schemas/{id}:
 *   get:
 *     tags:
 *       - 模型管理
 *     summary: 模型详情
 *     description:  模型详情
 *     parameters:
 *       - name: id
 *         description: ID或编码
 *         in: path
 *         required: true
 *         type: string
 *     responses:
 *       200:
 *         description: 模型详情
 */
router.get('/:id', async function (req, res, next) {

  var $match = {}
  var isById = tools.isValidObjectId(req.params.id)
  if (!isById) {
    $match.code = req.params.id
  } else {
    $match._id = mongoose.Types.ObjectId(req.params.id)
  }
  SchemaDef.aggregate([
    {
      $match: $match
    },
    {
      $lookup:
      {
        from: "categories",
        localField: "category",
        foreignField: "code",
        as: "category"
      }
    }
  ], (err, docs) => {
    if (err) return tools.handleError(res, err);
    if (docs.length === 0) return tools.handleError(res, "模型不存在");
    // mongo的aggregate返回的是数组结构，$match限制了最多只能返回一条
    var doc = docs[0]
    // 根上面的道理一样
    if (tools.isArray(doc.category) && doc.category.length > 0) {
      doc.category = doc.category[0]
    }
    return res.json({ status: 'success', data: doc })
  })
});

/**
 * @swagger
 * /cmdb/schemas/{id}:
 *   put:
 *     tags:
 *       - 模型管理
 *     summary: 更新模型
 *     description: 属性定义变更和所属分类变更，实例不会更新，变成游离属性
 *     parameters:
 *       - name: id
 *         description: ID或编码
 *         in: path
 *         required: true
 *         type: string
 *       - name: code
 *         description: 模型编码
 *         in: formData
 *         required: true
 *         type: string
 *       - name: name
 *         description: 模型名称
 *         in: formData
 *         required: true
 *         type: string
 *       - name: category
 *         description: 模型分类
 *         in: formData
 *         required: true
 *         type: string
 *       - name: attrs
 *         description: 属性定义
 *         in: formData
 *         required: false
 *         type: arrays
 *       - name: desc
 *         description: 模型描述
 *         in: formData
 *         required: false
 *         type: string
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.put('/:id', async function (req, res, next) {
  var { code, name, category, attrs, desc } = req.body
  var data = {
    code,
    name,
    category,
    attrs,
    desc,
    updated: Date.now()
  }
  var mAttrs = attrs || []
  for(var i=0; i<mAttrs.length; i++) {
    var validation = tools.validateSchemaAttr(mAttrs[i])
    if(validation !== true) {
      return res.json({ status: 'failure', data: validation })
    }
  }

  var isById = tools.isValidObjectId(req.params.id)
  var query = isById ? { _id: req.params.id } : { code: req.params.id }

  var afterUpdate = await SchemaDef.findOneAndUpdate(query, { $set: data }, { new: true });
  return res.json({ status: 'success', data: afterUpdate })
});

/**
  有lock限制
  默认实现是检查是否存在关系、是否存在实例
  TODO：删除逻辑需细化。 关联删除关系、实例、关系实例
*/
/**
 * @swagger
 *
 * /cmdb/schemas/{id}:
 *   delete:
 *     tags:
 *       - 模型管理
 *     summary: 删除模型
 *     description: 被锁定的模型，不能删除
 *     parameters:
 *       - name: id
 *         description: ID
 *         in: path
 *         required: true
 *         type: string
 *     responses:
 *       200:
 *         description: 存在关联关系、存在实例类，status=failure
 */
router.delete('/:id', async function (req, res, next) {
  const relationCount = await Relation.count({ $or: [{ src: req.params.id }, { dest: req.params.id }] })
  if (relationCount > 0) {
    return tools.handleError(res, '请先删除模型的关联关系');
  }

  const instanceCount = await Instance.count({ schemadef: req.params.id })
  if (instanceCount > 0) {
    return tools.handleError(res, '请先删除模型实例');
  }

  SchemaDef.findOneAndDelete({ _id: req.params.id, lock: false }, function (err) {
    if (err) return tools.handleError(res, err);
    return res.json({ status: 'success' })
  });
});

/**
 * @swagger
 *
 * /cmdb/schemas:
 *   post:
 *     tags:
 *       - 模型管理
 *     summary: 创建模型
 *     description: 创建模型
 *     parameters:
 *       - name: code
 *         description: 模型编码
 *         in: formData
 *         required: true
 *         type: string
 *       - name: name
 *         description: 模型名称
 *         in: formData
 *         required: true
 *         type: string
 *       - name: category
 *         description: 分类编码
 *         in: formData
 *         required: true
 *         type: string
 *       - name: desc
 *         description: 描述
 *         in: formData
 *         required: false
 *         type: string
 *       - name: attrs
 *         description: 属性定义
 *         in: formData
 *         required: false
 *         type: arrays
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.post('/', function (req, res, next) {
  var { code, name, category, attrs, desc } = req.body
  var data = { category, code, name, attrs, desc }
  var mAttrs = attrs || []

  for(var i=0; i<mAttrs.length; i++) {
    var validation = tools.validateSchemaAttr(mAttrs[i])
    if(validation !== true) {
      return res.json({ status: 'failure', data: validation })
    }
  }
  SchemaDef.create(data, function (err, doc) {
    if (err) return tools.handleError(res, err);
    return res.json({ status: 'success', data: doc })
  });
});

/**
 * @swagger
 *
 * /cmdb/schemas/{id}/relations:
 *   get:
 *     tags:
 *       - 模型管理
 *     summary: 查询模型关联
 *     description: 查询模型关联
 *     parameters:
 *       - name: id
 *         description: ID或编码
 *         in: path
 *         required: true
 *         type: string
 *     responses:
 *       200:
 *         description: 查询模型关联
 */
router.get('/:id/relations', async function (req, res, next) {

  var id = req.params.id
  var isById = tools.isValidObjectId(id)
  if (!isById) {
    var doc = await SchemaDef.findOne({ code: id })
    if (!doc) {
      return tools.handleError(res, '模型不存在')
    }
    id = doc._id
  }

  Relation.find({ $or: [{ src: id }, { dest: id }] })
    .populate('type')
    .populate({ path: 'src', select: '-versions' })
    .populate({ path: 'dest', select: '-versions' })
    .exec((err, docs) => {
      if (err) return tools.handleError(res, err);
      return res.json({ status: 'success', data: docs })
    })
});

/**
 * @swagger
 *
 * /cmdb/schemas/{id}/topo:
 *   get:
 *     tags:
 *       - 模型管理
 *     summary: 查询模型拓扑
 *     description: 查询模型拓扑
 *     parameters:
 *       - name: id
 *         description: ID或编码
 *         in: path
 *         required: true
 *         type: string
 *     responses:
 *       200:
 *         description: 查询模型拓扑
 */
router.get('/:id/topo', async function (req, res, next) {

  var isById = tools.isValidObjectId(req.params.id)
  var query = isById ? { _id: req.params.id } : { code: req.params.id }

  var schemaDef = await SchemaDef.findOne(query)
  if (!schemaDef) {
    return tools.handleError(res, '模型不存在');
  }

  var cached = await cache.exists(CACHE_INSTANCE, schemaDef.code + '::topo')
  if (cached) {
    var cachedData = await cache.get(CACHE_INSTANCE, schemaDef.code + '::topo')
    return res.json({ status: 'success', data: cachedData })
  }

  var schemaId = schemaDef._id.toString()
  let icon = await SchemaIcon.findOne({ schemadef: schemaId })

  var relations = await Relation.find({ $or: [{ src: schemaId }, { dest: schemaId }] })
    .populate('type')
    .populate({ path: 'src', select: '-versions' })
    .populate({ path: 'dest', select: '-versions' })

  var nodes = []
  var edges = []
  nodes.push({ id: schemaDef.code, _id: schemaDef._id, label: schemaDef.name, icon: icon ? icon.url : '' })

  // 只处理模型的直接关联关系
  for (var i = 0; i < relations.length; i++) {
    var r = relations[i];
    var srcId = r.src._id.toString();
    var destId = r.dest._id.toString();
    if (schemaId == srcId) {
      let icon = await SchemaIcon.findOne({ schemadef: destId })
      nodes.push({ id: r.dest.code, _id: r.dest._id, label: r.dest.name, icon: icon ? icon.url : '' })
      edges.push({ from: r.src.code, to: r.dest.code, arrows: 'to', label: r.type.srcdesc })
    } else {
      let icon = await SchemaIcon.findOne({ schemadef: srcId })
      nodes.push({ id: r.src.code, _id: r.src._id, label: r.src.name, icon: icon ? icon.url : '' });
      edges.push({ from: r.src.code, to: r.dest.code, arrows: 'from', label: r.type.destdesc });
    }
  }

  // 属性的外键关系
  var category = await Category.findOne({ code: schemaDef.category })
  var allAttrs = [...category.attrs, ...schemaDef.attrs]
  var foreigns = allAttrs.filter(attr => attr.dtype === 'foreign')
  for (var i = 0; i < foreigns.length; i++) {
    var attr = foreigns[i]
    var foreignSchema = await SchemaDef.findOne({ code: attr.dvalue }, { _id: 1, code: 1, name: 1 })
    var foreighIcon = ''
    if (!foreignSchema) {
      let icon = await SchemaIcon.findOne({ schemadef: foreignSchema._id })
      foreighIcon = icon ? icon.url : ''
    }
    if (!nodes.some(node => node.id === foreignSchema.code)) {
      nodes.push({ id: foreignSchema.code, _id: foreignSchema._id, label: foreignSchema.name, icon: foreighIcon })
    }
    edges.push({ from: schemaDef.code, to: foreignSchema.code, arrows: 'to', font: { multi: true }, label: '<b>外键</b>\n' })
  }

  cache.set(CACHE_INSTANCE, schemaDef.code + '::topo', { nodes, edges }, 60 * 1000) // 缓存60秒
  return res.json({ status: 'success', data: { nodes, edges } })
});

/**
 * @swagger
 *
 * /cmdb/schemas/{id}/relations/add:
 *   post:
 *     tags:
 *       - 模型管理
 *     summary: 添加关联
 *     description: 添加关联。约束有效值onetoone, onetomany, manytomany, manytoone
 *     parameters:
 *       - name: id
 *         description: ID或编码
 *         in: path
 *         required: true
 *         type: string
 *       - name: type
 *         description: 关联类型ID
 *         in: formData
 *         required: true
 *         type: string
 *       - name: src
 *         description: 源模型ID
 *         in: formData
 *         required: true
 *         type: string
 *       - name: dest
 *         description: 目标模型ID
 *         in: formData
 *         required: true
 *         type: string
 *       - name: restrain
 *         description: 约束
 *         in: formData
 *         required: true
 *         type: string
 *       - name: desc
 *         description: 描述
 *         in: formData
 *         required: false
 *         type: string
 *       - name: attrs
 *         description: 属性定义
 *         in: formData
 *         required: false
 *         type: arrays
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.put('/:id/relations/add', async function (req, res, next) {

  var isById = tools.isValidObjectId(req.params.id)
  var query = isById ? { _id: req.params.id } : { code: req.params.id }

  var schemaDef = await SchemaDef.findOne(query)
  if (!schemaDef) {
    return tools.handleError(res, '模型不存在');
  }

  var { type, src, dest, restrain, desc, attrs } = req.body
  var typeInst = await RelationType.findById(type)
  var srcInst = await SchemaDef.findById(src)
  var destInst = await SchemaDef.findById(dest)

  if (!typeInst || !srcInst || !destInst) {
    return tools.handleError(res, '关联类型，或源模型，或目标模型不存在');
  }

  var data = {
    type: typeInst._id,
    src: srcInst._id,
    dest: destInst._id,
    restrain,
    attrs,
    desc
  }

  // 没有则插入
  await Relation.updateOne({ src: srcInst._id, dest: destInst._id }, data, { upsert: true })
  var r = await Relation.findOne({ src: srcInst._id, dest: destInst._id })
  // 关系源模型保存关联引用 TODO: 有可能重复
  await SchemaDef.findByIdAndUpdate(src, { $push: { relations: r } });
  // 关系目标模型保存关联引用 TODO: 有可能重复
  await SchemaDef.findByIdAndUpdate(dest, { $push: { relations: r } });
  res.json({ status: 'success', data: r })
});

/**
 * @swagger
 *
 * /cmdb/schemas/{id}/relations/{rid}:
 *   delete:
 *     tags:
 *       - 模型管理
 *     summary: 删除关联关系
 *     description: 删除关联关系
 *     parameters:
 *       - name: id
 *         description: 模型ID或编码
 *         in: path
 *         required: true
 *         type: string
 *       - name: rid
 *         description: 关联关系ID
 *         in: path
 *         required: true
 *         type: string
 *     responses:
 *       200:
 *         description: 存在关联关系实例，status=failure
 */
router.delete('/:id/relations/:rid', async function (req, res, next) {

  const relation = await Relation.findById(req.params.rid)
  if (!relation) {
    return tools.handleError(res, '关联关系不存在')
  }

  const relationInstanceCount = await RelationInstance.count({ rela: req.params.rid })
  if (relationInstanceCount > 0) {
    return tools.handleError(res, '请先删除关联关系实例')
  }

  // 删除模型的关联关系引用
  await SchemaDef.updateMany({ _id: { $in: [relation.src, relation.dest] } }, { $pull: { relations: { $in: [relation._id] } } })

  Relation.findByIdAndDelete(req.params.rid, function (err) {
    if (err) return tools.handleError(res, err);
    return res.json({ status: 'success' })
  });
});

/**
 * @swagger
 *
 * /cmdb/schemas/{id}/tags:
 *   get:
 *     tags:
 *       - 模型管理
 *     summary: 查询模型标签
 *     description: 查询模型标签
 *     parameters:
 *       - name: id
 *         description: ID或编码
 *         in: path
 *         required: true
 *         type: string
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.get('/:id/tags', async function (req, res, next) {
  var isById = tools.isValidObjectId(req.params.id)
  var query = isById ? { _id: req.params.id } : { code: req.params.id }

  var schemaDef = await SchemaDef.findOne(query)
  if (!schemaDef) {
    return tools.handleError(res, '模型不存在');
  }

  var tags = await Tag.find({ schemadef: schemaDef._id }).populate({ path: 'schemadef', select: 'code name category attrs' })
  tags = tags.map(tag => {
    return { ...tag._doc }
  })
  if (tags.length > 0) {
    var categoryCode = tags[0].schemadef.category
    var category = await Category.findOne({ code: categoryCode })
    tags.forEach(tag => {
      tag.category = category._doc
    })
  }
  return res.json({ status: 'success', data: tags })

})

/**
 * @swagger
 *
 * /cmdb/schemas/{id}/icon:
 *   get:
 *     tags:
 *       - 模型管理
 *     summary: 查询模型图标
 *     description: base64
 *     parameters:
 *       - name: id
 *         description: ID或编码
 *         in: path
 *         required: true
 *         type: string
 *     responses:
 *       200:
 *         description: data.url=base64图标
 */
router.get('/:id/icon', async function (req, res, next) {
  var isById = tools.isValidObjectId(req.params.id)
  var query = isById ? { _id: req.params.id } : { code: req.params.id }

  var schemaDef = await SchemaDef.findOne(query)
  if (!schemaDef) {
    return tools.handleError(res, '模型不存在');
  }

  SchemaIcon.findOne({ schemadef: schemaDef._id }, (err, doc) => {
    if (err) return tools.handleError(res, err);
    return res.json({ status: 'success', data: doc })
  })
})

/**
 * @swagger
 *
 * /cmdb/schemas/{id}/icon:
 *   post:
 *     tags:
 *       - 模型管理
 *     summary: 设置图标
 *     description: 设置图标
 *     parameters:
 *       - name: id
 *         description: ID
 *         in: path
 *         required: true
 *         type: string
 *       - name: file
 *         description: 图片文件
 *         in: formData
 *         required: true
 *         type: file
 *     responses:
 *       200:
 *         description: status/data结构
 */
router.post('/:id/icon', async function (req, res, next) {
  let form = new formidable.IncomingForm();
  form.encoding = 'utf-8';
  form.keepExtensions = true;

  let schemaIcon = await SchemaIcon.findOne({ schemadef: req.params.id })

  form.parse(req, (err, fields, files) => {
    if (err) return tools.handleError(res, err);
    const imgPath = files.file.path;
    const prefix = "data:" + mineType.lookup(imgPath) + ";base64,";
    const icon = fs.readFileSync(imgPath);
    const base64str = Buffer.from(icon, 'binary').toString('base64')

    if (schemaIcon) {
      SchemaIcon.findByIdAndUpdate(schemaIcon._id, { schemadef: req.params.id, url: prefix + base64str }, { new: true }, function (err, doc) {
        if (err) return tools.handleError(res, err);
        return res.json({ status: 'success', data: doc })
      });
    } else {
      SchemaIcon.create({ schemadef: req.params.id, url: prefix + base64str }, function (err, doc) {
        if (err) return tools.handleError(res, err);
        return res.json({ status: 'success', data: doc })
      });
    }
  })
})

/**
 * 下载模型实例导入模板
 */
router.get('/:id/tpl', async function (req, res, next) {
  var schemaDef = await SchemaDef.findById(req.params.id)
  var category = await Category.findOne({ code: schemaDef.category })
  var allAttrs = [...category.attrs, ...schemaDef.attrs]
  var workbook = new Excel.Workbook();

  var schemaSheet = workbook.addWorksheet(schemaDef.name);
  var columns = [{ header: '名称', key: 'name', width: 24 }, { header: '唯一标识', key: 'code', width: 16 }, { header: '描述', key: 'desc', width: 24 }]

  // 定义列
  allAttrs.forEach(attr => {
    columns.push({ header: attr.name, key: attr.field, width: 16 })
  })


  schemaSheet.columns = columns;
  // 表头添加样式
  schemaSheet.getRow(1).alignment = { vertical: 'middle', horizontal: 'center' }
  schemaSheet.getRow(1).font = { bold: true }

  var helpSheet = workbook.addWorksheet('help')
  helpSheet.state = 'hidden'
  let helpSheetColSeq = 1
  for (var i = 0; i < allAttrs.length; i++) {
    var attr = allAttrs[i]

    // 外键、枚举、布尔、数据源四中数据类型，单元格数据验证（序列）
    if (!['foreign', 'enum', 'bool', 'datasource'].some(dtype => dtype === attr.dtype)) {
      continue
    }

    var values = []
    // 外键直接根据模型，查询所有模型实例
    if (attr.dtype === 'foreign') {
      var foreignSchema = await SchemaDef.findOne({ code: attr.dvalue })
      var insts = await Instance.find({ schemadef: foreignSchema._id }, { code: 1, name: 1 })
      values = insts.map(it => it.code + '$$' + it.name)
    } else if (attr.dtype === 'enum') {
      values = attr.enums.map(it => it.value + '$$' + it.label)
    } else if (attr.dtype === 'bool') {
      values = ['true$$是', 'false$$否']
      // 数据源需要远程调用API
    } else if (attr.dtype === 'datasource') {
      var ds = attr.datasource
      var address = ds.url.toLocaleLowerCase().startsWith('http') ? ds.url : url.resolve(settings.GATEWAY.BASE_URL, ds.url)
      var token = req.get("authentication")
      log.debug(address, token)
      try {
        const result = await superagent
          .get(address)
          .set('Authentication', token)
          .set('Accept', 'application/json, text/plain, */*')

        var data = ds.responseKey ? result.body[ds.responseKey] : result.body
        if (tools.isArray(data)) {
          values = data.map(it => it[ds.valueKey] + '$$' + it[ds.labelKey])
        }
      } catch (err) {
        log.error(err)
      }
    }

    if (values.length > 0) {
      helpSheet.getColumn(helpSheetColSeq).values = values
      var helpSheetColLetter = tools.getExcelColumnLetter(helpSheetColSeq)
      var formulae = ['help!$' + helpSheetColLetter + '$1:$' + helpSheetColLetter + '$' + values.length]
      helpSheetColSeq++

      var col = schemaSheet.getColumn(attr.field)
      for (var j = 0; j < 65536; j++) {
        schemaSheet.getCell(j + 2, col._number).dataValidation = {
          type: 'list',
          allowBlank: true,
          formulae: formulae
        }
      }
    }
  }

  var tmpDir = settings.TMP_PATH ? settings.TMP_PATH : os.tmpdir()
  var fileName = schemaDef.name + '资源导入模板.xlsx'
  fileName = encodeURI(fileName, "GBK")
  fileName = fileName.toString('iso8859-1')
  var tmpFile = path.join(tmpDir, fileName)
  await workbook.xlsx.writeFile(tmpFile);

  res.set({
    "Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    "Content-Disposition": "attachment; filename=" + fileName,
    "Access-Control-Expose-Headers": "Content-Disposition"
  });
  fs.createReadStream(tmpFile).pipe(res);
});

module.exports = router;
